package com.ansion.log;

import android.text.TextUtils;
import android.util.Log;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.ansion.groovy.GroovyArray;
import com.ansion.preference.TurboPreferenceUtils;
import com.tencent.mmkv.BuildConfig;

import java.util.HashMap;
import java.util.Locale;


/**
 * Description: 日志先不做封装，后续可以考虑很多非常好用的日志库(disk,memory)
 * <p>
 * 核心日志必须采用Android Studio的live template来打印，否则反编译还是会看到日志
 * <p>
 * live template模板如下：(logd)
 * <code>
 * if (VLog.OPEN_LOG) {
 * VLog.d("", "");
 * }
 * </code>
 * <p>
 * 增加如下代码到你的bash-profile:
 * <code>
 * function vlog(){
 * if [ ! -n "$1" ]; then
 * adb logcat -s VLog
 * else
 * adb logcat -s VLog | grep "$1"
 * fi
 * }
 * </code>
 * <p>
 * 查看日志的方法：
 * 1. 命令vlog ： 查看所有vlog打印的日志
 * 2. 命令vlog tag： 查看指定tag的日志
 * <p>
 * Created by ____lamer on 2019/4/2 3:09 PM
 */
public class VLog {
    public static final int INFO = Log.INFO;
    public static final int DEBUG = Log.DEBUG;
    public static final int VERBOSE = Log.VERBOSE;
    public static final int WARN = Log.WARN;
    public static final int ERROR = Log.ERROR;
    public static final int ALL = -1;

    public static void init(Logger fileLogger) {
        LOG.init(fileLogger);
    }

    public static String TAG = "VLog";

    private static final String DEFAULT_FOMART = "[%s](tn:%s) %s";

    public static boolean OPEN_LOG = BuildConfig.DEBUG;

    private static String makeLogStrings(@Nullable String tag, @Nullable String message) {
        tag = TextUtils.isEmpty(tag) ? "" : tag;
        String threadName = Thread.currentThread().getName();
        threadName = TextUtils.isEmpty(threadName) ? "" : threadName;
        message = TextUtils.isEmpty(message) ? "" : message;
        return String.format(DEFAULT_FOMART, tag, threadName, message);
    }

    public static void i(@Nullable String tag, @Nullable String msg, @Nullable Object... format) {
        if (OPEN_LOG)
            LOG.i(tag, msg, format);
    }

    public static void d(@Nullable String tag, @Nullable String msg, @Nullable Object... format) {
        if (OPEN_LOG)
            LOG.d(tag, msg, format);
    }

    public static void w(@Nullable String tag, @Nullable String msg, @Nullable Object... format) {
        if (OPEN_LOG)
            LOG.w(tag, msg, format);
    }

    public static void e(@Nullable String tag, @Nullable String msg, @Nullable Object... format) {
        if (OPEN_LOG)
            LOG.e(tag, msg, format);
    }

    public static void e(@NonNull Throwable e, @Nullable String tag, @Nullable String msg, @Nullable Object... format) {
        if (OPEN_LOG)
            LOG.e(tag, msg, e, format);
    }

    public static void e(@Nullable String tag, @NonNull Throwable e) {
        e(tag, getStackTraceString(e));
    }

    public static void v(@Nullable String tag, @Nullable String msg, @Nullable Object... format) {
        if (OPEN_LOG)
            LOG.v(tag, msg, format);
    }

    public static void eonly(final Throwable tr) {
        if (OPEN_LOG)
            LOG.logcat(LOG.ERROR, tr);

    }

    public static void saveCrashLOG(final Throwable tr) {
        if (OPEN_LOG)
            LOG.saveCrashToLog(tr);
    }

    public static void donly(final String tag, final String msg) {
        if (OPEN_LOG)
            LOG.logcat(LOG.DEBUG, tag, msg);
    }


    public static void se(String tag, String msg) {
        DebugLog.e(tag, msg);
    }

    public static void sd(String tag, String msg) {
        DebugLog.d(tag, msg);
    }

    public static void waste(@Nullable String msg, @Nullable Object... format) {
        if (OPEN_LOG)
            LOG.d("Waste", msg, format);
    }

    /**
     * 必须调用过tick方法记录起始时间
     *
     * @param msg    打印的message
     * @param key    tick的key
     * @param format format object
     */
    public static void stopD( /* tick key */@NonNull String key, @Nullable String msg, @Nullable Object... format) {
        if (OPEN_LOG) {
            String timeString = " >> " + stop(key).getWaste() + "ms";
            LOG.d("Waste", msg + timeString, format);
        }
    }


    public static void flushLog() {
        LOG.flushLog();
    }

    @NonNull
    public static String getStackTraceString(@NonNull Throwable tr) {
        return Log.getStackTraceString(tr);
    }

    public static void printStackTrace(@NonNull String tag) {
        Log.e(tag, getStackTraceString(new Exception()));
    }


    //------------------ Debug专用的时间统计-----------------------
    private static HashMap<String, Time> mScope;

    private static synchronized void addScope(String key, Time time) {
        if (mScope == null) {
            mScope = new HashMap<>();
        }
        mScope.put(key, time);
    }

    private static synchronized Time getScope(String key) {
        if (mScope == null) {
            return null;
        }
        return mScope.get(key);
    }

    public static void tick(String key) {
        if (OPEN_LOG) {
            Time time = new Time();
            time.start = System.currentTimeMillis();
            addScope(key, time);
        }
    }

    @NonNull
    public static String getStackTrace() {
        return Log.getStackTraceString(new Exception("Stack trace"));
    }

    @Nullable
    public static String safeFormat(@Nullable String message, @Nullable Object... format) {
        try {
            if (message == null
                    || format == null) {
                return null;
            }
            //transform format value
            final int count = format.length;
            if (count > 0) {
                //maybe not strings...
                Object[] formatStrings = new Object[format.length];
                for (int i = 0; i < format.length; i++) {
                    formatStrings[i] = objToStr(format[i]);
                }
                return String.format(Locale.ENGLISH, message, formatStrings);
            }
            return message;
        } catch (Throwable t) {
            VLog.d(TAG, "format exception \n %s", Log.getStackTraceString(t));
            return "format error [" + message + "] with (" + GroovyArray.join(format, ",") + ")";
        }
    }

    /**
     * 将object变成string，但是也有特殊的情况。
     * 比如%d还是应该是Integer或者int。
     *
     * @param obj 输入的object
     * @return 返回的format格式
     */
    private static Object objToStr(@Nullable Object obj) {
        if (obj == null) {
            return "nullptr";
        } else if (obj instanceof Throwable) {
            return Log.getStackTraceString((Throwable) obj);
        } else {
            return obj;
        }
    }

    @NonNull
    public static Time stop(String key) {
        Time time = getScope(key);
        if (time == null) {
            return new Time() {
                @Override
                public int getWasteSec() {
                    return -1;
                }

                @Override
                public long getWaste() {
                    return -1L;
                }
            };
        } else {
            time.end = System.currentTimeMillis();
            return time;
        }
    }


    public static class Time {
        private long start;
        private long end;

        public long getWaste() {
            return end - start;
        }

        public int getWasteSec() {
            return (int) (getWaste() / 1000);
        }
    }

    public interface ILogger {
        /**
         * Send a {@link Log#VERBOSE} log message.
         *
         * @param tag Used to identify the source of a log message.  It usually identifies
         *            the class or activity where the log call occurs.
         * @param msg The message you would like logged.
         */
        int v(String tag, String msg);

        /**
         * Send a {@link Log#VERBOSE} log message and log the exception.
         *
         * @param tag Used to identify the source of a log message.  It usually identifies
         *            the class or activity where the log call occurs.
         * @param msg The message you would like logged.
         * @param tr  An exception to log
         */
        int v(String tag, String msg, Throwable tr);

        /**
         * Send a {@link Log#DEBUG} log message.
         *
         * @param tag Used to identify the source of a log message.  It usually identifies
         *            the class or activity where the log call occurs.
         * @param msg The message you would like logged.
         */
        int d(String tag, String msg);

        /**
         * Send a {@link Log#DEBUG} log message and log the exception.
         *
         * @param tag Used to identify the source of a log message.  It usually identifies
         *            the class or activity where the log call occurs.
         * @param msg The message you would like logged.
         * @param tr  An exception to log
         */
        int d(String tag, String msg, Throwable tr);

        /**
         * Send an {@link Log#INFO} log message.
         *
         * @param tag Used to identify the source of a log message.  It usually identifies
         *            the class or activity where the log call occurs.
         * @param msg The message you would like logged.
         */
        int i(String tag, String msg);

        /**
         * Send a {@link Log#INFO} log message and log the exception.
         *
         * @param tag Used to identify the source of a log message.  It usually identifies
         *            the class or activity where the log call occurs.
         * @param msg The message you would like logged.
         * @param tr  An exception to log
         */
        int i(String tag, String msg, Throwable tr);

        /**
         * Send a {@link Log#WARN} log message.
         *
         * @param tag Used to identify the source of a log message.  It usually identifies
         *            the class or activity where the log call occurs.
         * @param msg The message you would like logged.
         */
        int w(String tag, String msg);

        /**
         * Send a {@link Log#WARN} log message and log the exception.
         *
         * @param tag Used to identify the source of a log message.  It usually identifies
         *            the class or activity where the log call occurs.
         * @param msg The message you would like logged.
         * @param tr  An exception to log
         */
        int w(String tag, String msg, Throwable tr);


        /*
         * Send a {@link #WARN} log message and log the exception.
         * @param tag Used to identify the source of a log message.  It usually identifies
         *        the class or activity where the log call occurs.
         * @param tr An exception to log
         */
        int w(String tag, Throwable tr);

        /**
         * Send an {@link Log#ERROR} log message.
         *
         * @param tag Used to identify the source of a log message.  It usually identifies
         *            the class or activity where the log call occurs.
         * @param msg The message you would like logged.
         */
        int e(String tag, String msg);

        /**
         * Send a {@link Log#ERROR} log message and log the exception.
         *
         * @param tag Used to identify the source of a log message.  It usually identifies
         *            the class or activity where the log call occurs.
         * @param msg The message you would like logged.
         * @param tr  An exception to log
         */
        int e(String tag, String msg, Throwable tr);

    }

    /**
     * 将日志存在本地,方便开发阶段查看.
     */
    public static class DebugLog {
        public static String TAG = "DEBUG_LOG";

        public static void d(String tag, String msg) {
            if (VLog.OPEN_LOG) {
                VLog.d(tag, msg);
            }
            TurboPreferenceUtils.getInstance().putString(TAG, formatLog(tag, msg));
        }

        public static void e(String tag, String msg) {
            if (VLog.OPEN_LOG) {
                VLog.e(tag, msg);
            }
            TurboPreferenceUtils.getInstance().putString(TAG, formatLog(tag, msg));
        }

        public static String getLog() {
            return TurboPreferenceUtils.getInstance().getString(TAG, "");
        }

        public static void clear() {
            TurboPreferenceUtils.getInstance().putString(TAG, "");
        }

        private static String formatLog(String tag, String msg) {
            String threadName = Thread.currentThread().getName();
            threadName = TextUtils.isEmpty(threadName) ? "" : threadName;
            String plog = TurboPreferenceUtils.getInstance().getString(TAG, "");
            String clog = String.format("Log tag : %s, Log message : %s, Log thread : %s", tag, msg, threadName);
            StringBuffer log = new StringBuffer();
            log.append(plog);
            log.append("-----------------------------------------");
            log.append("\n");
            log.append("\n");
            log.append(clog);
            return log.toString();
        }
    }

    /***
     * 老版本的AZus日志
     */
    public static class AZus {

        public static void v(String tag, String msg) {
            if (!OPEN_LOG) return;
            VLog.v(tag, msg);
        }

        public static void v(String tag, String msg, Throwable tr) {
            if (!OPEN_LOG) return;
            VLog.v(tag, msg, tr);
        }

        public static void d(String tag, String msg) {
            if (!OPEN_LOG) return;
            VLog.d(tag, msg);
        }

        public static void d(String tag, String msg, Throwable tr) {
            if (!OPEN_LOG) return;
            VLog.d(tag, msg, tr);
        }

        public static void i(String tag, String msg) {
            if (!OPEN_LOG) return;
            VLog.i(tag, msg);
        }

        public static void i(String tag, String msg, Throwable tr) {
            if (!OPEN_LOG) return;
            VLog.i(tag, msg, tr);
        }

        public static void w(String tag, String msg) {
            if (!OPEN_LOG) return;
            VLog.w(tag, msg);
        }

        public static void w(String tag, String msg, Throwable tr) {
            if (!OPEN_LOG) return;
            VLog.w(tag, msg, tr);
        }

        public static void w(String tag, Throwable tr) {
            if (!OPEN_LOG) return;
            VLog.w(tag, tr.getMessage());
        }

        public static void e(String tag, String msg) {
            if (!OPEN_LOG) return;
            VLog.e(tag, msg);
        }

        public static void e(String tag, String msg, Throwable tr) {
            if (!OPEN_LOG) return;
            VLog.e(tag, msg, tr);
        }

    }

    /**
     *
     * 注意：由于日志打印开关设置需要读CC，文件日志打印必须在BOTApplication.onCreate执行完毕之后做，否则会出现所有日志一直不落盘。
     * 这并不是普通的打日志，而且会写入文件；目前采用微信的xlog进行写入。
     * 大家后续可以放心写入，log路径为data/data/{pkgname}/files/xlogCache/
     * 会保留7天的日志，日志解密key见wiki："xlog decrypt guide.md"
     */
    public static class FileLog {
        public static boolean OPEN_LOG = true;//单独的cc配置

        public static void i(@Nullable String tag, @Nullable String msg, @Nullable Object... format) {
            if (OPEN_LOG)
                LOG.i(tag, msg, format);
        }

        public static void d(@Nullable String tag, @Nullable String msg, @Nullable Object... format) {
            if (OPEN_LOG)
                LOG.d(tag, msg, format);
        }

        public static void w(@Nullable String tag, @Nullable String msg, @Nullable Object... format) {
            if (OPEN_LOG)
                LOG.w(tag, msg, format);
        }

        public static void e(@Nullable String tag, @Nullable String msg, @Nullable Object... format) {
            if (OPEN_LOG)
                LOG.e(tag, msg, format);
        }

        public static void e(@NonNull Throwable e, @Nullable String tag, @Nullable String msg, @Nullable Object... format) {
            if (OPEN_LOG)
                LOG.e(tag, msg, e, format);
        }

        public static void e(@Nullable String tag, @NonNull Throwable e) {
            if (OPEN_LOG)
                e(tag, getStackTraceString(e));
        }

        public static void v(@Nullable String tag, @Nullable String msg, @Nullable Object... format) {
            if (OPEN_LOG)
                LOG.v(tag, msg, format);
        }

        public static void eonly(final Throwable tr) {
            if (OPEN_LOG)
                LOG.logcat(LOG.ERROR, tr);
        }


        public static void donly(final String tag, final String msg) {
            if (OPEN_LOG)
                LOG.logcat(LOG.DEBUG, tag, msg);
        }


    }

    public static class VoipFileLog {
        public static boolean OPEN_LOG = false;

        public static void i(@Nullable String tag, @Nullable String msg, @Nullable Object... format) {
            if (OPEN_LOG)
                LOG.i(tag, msg, format);
        }

        public static void d(@Nullable String tag, @Nullable String msg, @Nullable Object... format) {
            if (OPEN_LOG)
                LOG.d(tag, msg, format);
        }

        public static void w(@Nullable String tag, @Nullable String msg, @Nullable Object... format) {
            if (OPEN_LOG)
                LOG.w(tag, msg, format);
        }

        public static void e(@Nullable String tag, @Nullable String msg, @Nullable Object... format) {
            if (OPEN_LOG)
                LOG.e(tag, msg, format);
        }

        public static void e(@NonNull Throwable e, @Nullable String tag, @Nullable String msg, @Nullable Object... format) {
            if (OPEN_LOG)
                LOG.e(tag, msg, e, format);
        }

        public static void e(@Nullable String tag, @NonNull Throwable e) {
            if (OPEN_LOG)
                e(tag, getStackTraceString(e));
        }

        public static void v(@Nullable String tag, @Nullable String msg, @Nullable Object... format) {
            if (OPEN_LOG)
                LOG.v(tag, msg, format);
        }

        public static void eonly(final Throwable tr) {
            if (OPEN_LOG)
                LOG.logcat(LOG.ERROR, tr);
        }


        public static void donly(final String tag, final String msg) {
            if (OPEN_LOG)
                LOG.logcat(LOG.DEBUG, tag, msg);
        }


    }
}
